import { vec3 } from "gl-matrix";
import * as cornerstoneTools from "@cornerstonejs/tools";
import { utilities as csUtils } from "@cornerstonejs/core";

const { Enums: csToolsEnums, CobbAngleTool, annotation, drawing, utilities } = cornerstoneTools;

const { transformWorldToIndex } = csUtils;
const { ChangeTypes } = csToolsEnums;
const { getAnnotations, triggerAnnotationModified } = annotation.state;
const { isAnnotationLocked } = annotation.locking;
const { isAnnotationVisible } = annotation.visibility;
const {
	drawHandles: drawHandlesSvg,
	drawTextBox: drawTextBoxSvg,
	drawLine: drawLineSvg,
	drawLinkedTextBox: drawLinkedTextBoxSvg
} = drawing;

const { getCalibratedLengthUnitsAndScale, throttle } = utilities;
const { getTextBoxCoordsCanvas } = utilities.drawing;

const midPoint2 = (...args) => {
	const ret = args[0].length === 2 ? [0, 0] : [0, 0, 0];
	const len = args.length;
	for (const arg of args) {
		ret[0] += arg[0] / len;
		ret[1] += arg[1] / len;
		if (ret.length === 3) {
			ret[2] += arg[2] / len;
		}
	}
	return ret;
};

class CTRTool extends CobbAngleTool {
	static toolName = "CardiothoracicRatio";

	// constructor(options = { getTextLines: defaultGetTextLines }) {
	// 	super(options);
	// }

	constructor(
		toolProps = {},
		defaultToolProps = {
			supportedInteractionTypes: ["Mouse", "Touch"],
			configuration: {
				shadow: true,
				preventHandleOutsideImage: false,
				getTextLines: defaultGetTextLines,
				showLinesText: true
			}
		}
	) {
		super(toolProps, defaultToolProps);
		this._throttledCalculateCachedStats = throttle(this._calculateCachedStats, 25, {
			trailing: true
		});
	}

	_calculateLength(pos1, pos2) {
		const dx = pos1[0] - pos2[0];
		const dy = pos1[1] - pos2[1];
		const dz = pos1[2] - pos2[2];

		return Math.sqrt(dx * dx + dy * dy + dz * dz);
	}

	_calculateCachedStats(annotation, renderingEngine, enabledElement) {
		const data = annotation.data;

		// Until we have all four anchors bail out
		if (data.handles.points.length !== 4) {
			return;
		}

		const seg1 = [null, null];
		const seg2 = [null, null];
		let minDist = Number.MAX_VALUE;

		// Order the endpoints of each line segment such that seg1[1] and seg2[0]
		// are the closest (Euclidean distance-wise) to each other. Thus
		// the angle formed between the vectors seg1[1]->seg1[0] and seg2[0]->seg[1]
		// is calculated.
		// The assumption here is that the Cobb angle line segments are drawn
		// such that the segments intersect nearest the segment endpoints
		// that are closest AND those closest endpoints are the tails of the
		// vectors used to calculate the angle between the vectors/line segments.
		for (let i = 0; i < 2; i += 1) {
			for (let j = 2; j < 4; j += 1) {
				const dist = vec3.distance(data.handles.points[i], data.handles.points[j]);
				if (dist < minDist) {
					minDist = dist;
					seg1[1] = data.handles.points[i];
					seg1[0] = data.handles.points[(i + 1) % 2];
					seg2[0] = data.handles.points[j];
					seg2[1] = data.handles.points[2 + ((j - 1) % 2)];
				}
			}
		}
		const { viewport } = enabledElement;
		const { element } = viewport;

		const canvasPoints = data.handles.points.map(p => viewport.worldToCanvas(p));

		const firstLine = [canvasPoints[0], canvasPoints[1]];
		const secondLine = [canvasPoints[2], canvasPoints[3]];

		const mid1 = midPoint2(firstLine[0], firstLine[1]);
		const mid2 = midPoint2(secondLine[0], secondLine[1]);

		const { arc1Start, arc1End, arc2End, arc2Start, arc1Angle, arc2Angle } =
			this.getArcsStartEndPoints({
				firstLine,
				secondLine,
				mid1,
				mid2
			});

		const wdArc1Start = data.handles.points[0];
		const wdArc1End = data.handles.points[1];
		const wdArc2Start = data.handles.points[2];
		const wdArc2End = data.handles.points[3];

		const { cachedStats } = data;
		const targetIds = Object.keys(cachedStats);

		for (let i = 0; i < targetIds.length; i++) {
			const targetId = targetIds[i];

			// Calculate the length of each line segment
			const image = this.getTargetImageData(targetId);
			if (!image) {
				continue;
			}

			const { imageData } = image;

			let index1 = transformWorldToIndex(imageData, wdArc1Start);
			let index2 = transformWorldToIndex(imageData, wdArc1End);
			let handles = [index1, index2];
			const len1 = getCalibratedLengthUnitsAndScale(image, handles);
			const length1 = this._calculateLength(wdArc1Start, wdArc1End) / len1.scale;

			index1 = transformWorldToIndex(imageData, wdArc2Start);
			index2 = transformWorldToIndex(imageData, wdArc2End);
			handles = [index1, index2];
			const { scale, unit } = getCalibratedLengthUnitsAndScale(image, handles);
			const length2 = this._calculateLength(wdArc2Start, wdArc2End) / scale;

			const ratio = length1 / length2;

			/////////////////////////////////////

			cachedStats[targetId] = {
				// angle: angleBetweenLines(seg1, seg2),
				length1,
				length2,
				unit,
				ratio,
				// arc1Angle,
				// arc2Angle,
				points: {
					canvas: {
						arc1Start,
						arc1End,
						arc2End,
						arc2Start
					},
					world: {
						arc1Start: viewport.canvasToWorld(arc1Start),
						arc1End: viewport.canvasToWorld(arc1End),
						arc2End: viewport.canvasToWorld(arc2End),
						arc2Start: viewport.canvasToWorld(arc2Start)
					}
				}
			};
		}

		const invalidated = annotation.invalidated;
		annotation.invalidated = false;

		// Dispatching annotation modified only if it was invalidated
		if (invalidated) {
			triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated);
		}

		return cachedStats;
	}

	/**
	 * it is used to draw the probe annotation in each
	 * request animation frame. It calculates the updated cached statistics if
	 * data is invalidated and cache it.
	 *
	 * @param enabledElement - The Cornerstone's enabledElement.
	 * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
	 */
	renderAnnotation = (enabledElement, svgDrawingHelper) => {
		let renderStatus = false;

		const { viewport } = enabledElement;
		const { element } = viewport;

		let annotations = getAnnotations(this.getToolName(), element);

		// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
		if (!annotations?.length) {
			return renderStatus;
		}

		annotations = this.filterInteractableAnnotationsForElement(element, annotations);

		if (!annotations?.length) {
			return renderStatus;
		}

		const targetId = this.getTargetId(viewport);
		const renderingEngine = viewport.getRenderingEngine();

		const styleSpecifier = {
			toolGroupId: this.toolGroupId,
			toolName: this.getToolName(),
			viewportId: enabledElement.viewport.id
		};

		// Draw SVG
		for (let i = 0; i < annotations.length; i++) {
			const annotation = annotations[i];
			const { annotationUID, data } = annotation;
			const { points, activeHandleIndex } = data.handles;

			styleSpecifier.annotationUID = annotationUID;

			const { color, lineWidth, lineDash } = this.getAnnotationStyle({
				annotation,
				styleSpecifier
			});

			const canvasCoordinates = points.map(p => viewport.worldToCanvas(p));

			// WE HAVE TO CACHE STATS BEFORE FETCHING TEXT
			if (!data.cachedStats[targetId] || data.cachedStats[targetId].ratio == null) {
				data.cachedStats[targetId] = {
					// angle: null,
					length1: null,
					length2: null,
					unit: null,
					ratio: null,
					// arc1Angle: null,
					// arc2Angle: null,
					points: {
						world: {
							arc1Start: null,
							arc1End: null,
							arc2Start: null,
							arc2End: null
							// arc1Angle: null,
							// arc2Angle: null
						},
						canvas: {
							arc1Start: null,
							arc1End: null,
							arc2Start: null,
							arc2End: null
							// arc1Angle: null,
							// arc2Angle: null
						}
					}
				};

				this._calculateCachedStats(annotation, renderingEngine, enabledElement);
			} else if (annotation.invalidated) {
				this._throttledCalculateCachedStats(annotation, renderingEngine, enabledElement);
			}

			let activeHandleCanvasCoords;

			if (
				!isAnnotationLocked(annotationUID) &&
				!this.editData &&
				activeHandleIndex !== null
			) {
				// Not locked or creating and hovering over handle, so render handle.
				activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
			}

			// If rendering engine has been destroyed while rendering
			if (!viewport.getRenderingEngine()) {
				console.warn("Rendering Engine has been destroyed");
				return renderStatus;
			}

			if (!isAnnotationVisible(annotationUID)) {
				continue;
			}

			if (activeHandleCanvasCoords) {
				const handleGroupUID = "0";

				drawHandlesSvg(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, {
					color,
					lineDash,
					lineWidth
				});
			}

			const firstLine = [canvasCoordinates[0], canvasCoordinates[1]];
			const secondLine = [canvasCoordinates[2], canvasCoordinates[3]];

			let lineUID = "line1";
			drawLineSvg(svgDrawingHelper, annotationUID, lineUID, firstLine[0], firstLine[1], {
				color,
				width: lineWidth,
				lineDash
			});

			renderStatus = true;

			// Don't add the stats until annotation has 4 anchor points
			if (canvasCoordinates.length < 4) {
				return renderStatus;
			}

			lineUID = "line2";

			drawLineSvg(svgDrawingHelper, annotationUID, lineUID, secondLine[0], secondLine[1], {
				color,
				width: lineWidth,
				lineDash
			});

			lineUID = "linkLine";
			const mid1 = midPoint2(firstLine[0], firstLine[1]);
			const mid2 = midPoint2(secondLine[0], secondLine[1]);
			drawLineSvg(svgDrawingHelper, annotationUID, lineUID, mid1, mid2, {
				color,
				lineWidth: "1",
				lineDash: "1,4"
			});

			// Calculating the arcs
			const { arc1Start, arc1End, arc2End, arc2Start } =
				data.cachedStats[targetId].points.canvas;
			const { length1, length2, unit } = data.cachedStats[targetId];

			if (!data.cachedStats[targetId]?.ratio) {
				continue;
			}

			const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
			if (!options.visibility) {
				data.handles.textBox = {
					hasMoved: false,
					worldPosition,
					worldBoundingBox: {
						topLeft,
						topRight,
						bottomLeft,
						bottomRight
					}
				};
				continue;
			}

			const textLines = this.configuration.getTextLines(data, targetId);

			if (!data.handles.textBox.hasMoved) {
				const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);

				data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
			}

			const textBoxPosition = viewport.worldToCanvas(data.handles.textBox.worldPosition);
			textBoxPosition[1] -= 50;

			const textBoxUID = "ctrRatioText";
			const boundingBox = drawLinkedTextBoxSvg(
				svgDrawingHelper,
				annotationUID,
				textBoxUID,
				textLines,
				textBoxPosition,
				canvasCoordinates,
				{},
				options
			);

			const { x: left, y: top, width, height } = boundingBox;

			data.handles.textBox.worldBoundingBox = {
				topLeft: viewport.canvasToWorld([left, top]),
				topRight: viewport.canvasToWorld([left + width, top]),
				bottomLeft: viewport.canvasToWorld([left, top + height]),
				bottomRight: viewport.canvasToWorld([left + width, top + height])
			};

			if (this.configuration.showLinesText) {
				const arc1TextBoxUID = "lineText1";

				const arc1TextLine = [`${length1.toFixed(2)} ${unit}`];

				const arch1TextPosCanvas = midPoint2(arc1Start, arc1End);
				arch1TextPosCanvas[0] -= 30;
				arch1TextPosCanvas[1] = arc1Start[1] - 24;

				drawTextBoxSvg(
					svgDrawingHelper,
					annotationUID,
					arc1TextBoxUID,
					arc1TextLine,
					arch1TextPosCanvas,
					{
						...options,
						padding: 3
					}
				);

				const arc2TextBoxUID = "lineText2";

				const arc2TextLine = [`${length2.toFixed(2)} ${unit}`];

				const arch2TextPosCanvas = midPoint2(arc2Start, arc2End);
				arch2TextPosCanvas[0] -= 30;
				arch2TextPosCanvas[1] = arc2Start[1] - 24;

				drawTextBoxSvg(
					svgDrawingHelper,
					annotationUID,
					arc2TextBoxUID,
					arc2TextLine,
					arch2TextPosCanvas,
					{
						...options,
						padding: 3
					}
				);
			}
		}

		return renderStatus;
	};
}

function defaultGetTextLines(data, targetId) {
	const cachedVolumeStats = data.cachedStats[targetId];
	const { ratio } = cachedVolumeStats;

	if (ratio === undefined) {
		return;
	}

	const textLines = [`${ratio.toFixed(2)}`];

	return textLines;
}

export default CTRTool;
